/**
 * Licensed to JumpMind Inc under one or more contributor
 * license agreements.  See the NOTICE file distributed
 * with this work for additional information regarding
 * copyright ownership.  JumpMind Inc licenses this file
 * to you under the GNU General Public License, version 3.0 (GPLv3)
 * (the "License"); you may not use this file except in compliance
 * with the License.
 *
 * You should have received a copy of the GNU General Public License,
 * version 3.0 (GPLv3) along with this library; if not, see
 * <http://www.gnu.org/licenses/>.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jumpmind.symmetric;

import java.net.CookieHandler;
import java.net.CookieManager;

import javax.servlet.ServletContext;

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.jumpmind.properties.TypedProperties;
import org.jumpmind.security.SecurityConstants;
import org.jumpmind.symmetric.common.ServerConstants;
import org.jumpmind.symmetric.common.SystemConstants;
import org.jumpmind.symmetric.web.ServletUtils;
import org.jumpmind.symmetric.web.SymmetricEngineHolder;
import org.jumpmind.symmetric.web.WebConstants;
import org.jumpmind.symmetric.web.rest.RestService;
import org.jumpmind.util.AppUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.stereotype.Component;

/**
 * SymmetricDS服务器配置类，用于Spring Boot环境。
 * 不再使用嵌入式Jetty服务器，而是依赖Spring Boot的内置Tomcat。
 */
public class SymmetricWebServer implements ServletContextInitializer, ApplicationContextAware {
    private static final Logger log = LoggerFactory.getLogger(SymmetricWebServer.class);
    protected static final String DEFAULT_WEBAPP_DIR = System.getProperty(SystemConstants.SYSPROP_WEB_DIR, AppUtils.getSymHome() + "/web");
    public static final String DEFAULT_HTTP_PORT = System.getProperty(SystemConstants.SYSPROP_DEFAULT_HTTP_PORT, "31415");
    public static final String DEFAULT_HTTPS_PORT = System.getProperty(SystemConstants.SYSPROP_DEFAULT_HTTPS_PORT, "31417");
    public static final int DEFAULT_MAX_IDLE_TIME = 90000;

    /**
     * The type of HTTP connection to create for this SymmetricDS web server
     */
    public enum Mode {
        HTTP, HTTPS, MIXED;
    }

    /**
     * 这些字段保留用于兼容性，但在Spring Boot环境中不再使用
     */
    protected boolean join = true;
    protected String webHome = "/";
    protected int maxIdleTime = DEFAULT_MAX_IDLE_TIME;
    protected boolean httpEnabled = true;
    protected int httpPort = Integer.parseInt(DEFAULT_HTTP_PORT);
    protected boolean httpsEnabled = false;
    protected boolean https2Enabled = false;
    protected int httpsPort = -1;
    protected String propertiesFile = null;
    protected String host = null;
    protected boolean noNio = false;
    protected boolean noDirectBuffer = false;
    protected String webAppDir = DEFAULT_WEBAPP_DIR;
    protected String name = "SymmetricDS";
    protected String httpSslVerifiedServerNames = "all";
    protected boolean httpsNeedClientAuth = false;
    protected boolean httpsWantClientAuth = false;
    protected String allowDirListing = "false";
    protected String disallowedMethods = "OPTIONS";
    protected String allowedMethods = "";
    protected boolean allowSelfSignedCerts = true;
    protected boolean accessLogEnabled = false;
    protected String accessLogFile;
    protected String cookieName;
    
    /**
     * Spring ApplicationContext，用于获取服务
     */
    private ApplicationContext applicationContext;
    
    /**
     * ServletContext，由Spring Boot管理
     */
    private ServletContext servletContext;

    public SymmetricWebServer() {
        this(null, DEFAULT_WEBAPP_DIR);
    }

    public SymmetricWebServer(String propertiesUrl) {
        this(propertiesUrl, DEFAULT_WEBAPP_DIR);
    }

    public SymmetricWebServer(int maxIdleTime, String propertiesUrl) {
        this(propertiesUrl, DEFAULT_WEBAPP_DIR);
        this.maxIdleTime = maxIdleTime;
    }

    public SymmetricWebServer(String webDirectory, int maxIdleTime, String propertiesUrl, boolean join, boolean noNio, boolean noDirectBuffer) {
        this(propertiesUrl, webDirectory);
        this.maxIdleTime = maxIdleTime;
        this.join = join;
        this.noDirectBuffer = noDirectBuffer;
        this.noNio = noNio;
    }

    public SymmetricWebServer(String propertiesUrl, String webappDir) {
        this.propertiesFile = propertiesUrl;
        this.webAppDir = webappDir;
        initFromProperties();
    }

    protected final void initFromProperties() {
        try {
            Class.forName(AbstractCommandLauncher.class.getName());
        } catch (ClassNotFoundException e) {
        }
        TypedProperties serverProperties = new TypedProperties(System.getProperties());
        httpEnabled = serverProperties.is(ServerConstants.HTTP_ENABLE,
                Boolean.parseBoolean(System.getProperty(ServerConstants.HTTP_ENABLE, "true")));
        httpsEnabled = serverProperties.is(ServerConstants.HTTPS_ENABLE,
                Boolean.parseBoolean(System.getProperty(ServerConstants.HTTPS_ENABLE, "true")));
        https2Enabled = serverProperties.is(ServerConstants.HTTPS2_ENABLE,
                Boolean.parseBoolean(System.getProperty(ServerConstants.HTTPS2_ENABLE, "false")));
        httpPort = serverProperties.getInt(ServerConstants.HTTP_PORT,
                Integer.parseInt(System.getProperty(ServerConstants.HTTP_PORT, "" + httpPort)));
        httpsPort = serverProperties.getInt(ServerConstants.HTTPS_PORT,
                Integer.parseInt(System.getProperty(ServerConstants.HTTPS_PORT, "" + httpsPort)));
        host = serverProperties.get(ServerConstants.HOST_BIND_NAME, System.getProperty(ServerConstants.HOST_BIND_NAME, host));
        httpSslVerifiedServerNames = serverProperties.get(ServerConstants.HTTPS_VERIFIED_SERVERS,
                System.getProperty(ServerConstants.HTTPS_VERIFIED_SERVERS, httpSslVerifiedServerNames));
        allowSelfSignedCerts = serverProperties.is(ServerConstants.HTTPS_ALLOW_SELF_SIGNED_CERTS,
                Boolean.parseBoolean(System.getProperty(ServerConstants.HTTPS_ALLOW_SELF_SIGNED_CERTS, "" + allowSelfSignedCerts)));
        allowDirListing = serverProperties.get(ServerConstants.SERVER_ALLOW_DIR_LISTING, "false");
        allowedMethods = serverProperties.get(ServerConstants.SERVER_ALLOW_HTTP_METHODS, "");
        disallowedMethods = serverProperties.get(ServerConstants.SERVER_DISALLOW_HTTP_METHODS, "OPTIONS");
        httpsNeedClientAuth = serverProperties.is(ServerConstants.HTTPS_NEED_CLIENT_AUTH, false);
        httpsWantClientAuth = serverProperties.is(ServerConstants.HTTPS_WANT_CLIENT_AUTH, false);
        accessLogEnabled = serverProperties.is(ServerConstants.SERVER_ACCESS_LOG_ENABLED, false);
        accessLogFile = serverProperties.get(ServerConstants.SERVER_ACCESS_LOG_FILE);
        cookieName = serverProperties.get(ServerConstants.SERVER_COOKIE_NAME);
        if (serverProperties.is(ServerConstants.SERVER_HTTP_COOKIES_ENABLED, false)) {
            if (CookieHandler.getDefault() == null) {
                CookieHandler.setDefault(new CookieManager());
            }
        }
        if (StringUtils.isBlank(cookieName)) {
            cookieName = WebConstants.SESSION_PREFIX + (httpsPort > 0 && httpsEnabled ? httpsPort : "")
                    + (httpEnabled && httpPort > 0 ? "_" + httpPort : "");
        }
    }

    /**
     * 获取ServletContext
     * 在Spring Boot环境中，ServletContext由Spring Boot管理
     */
    protected ServletContext getServletContext() {
        return servletContext;
    }

    /**
     * 获取RestService
     * 在Spring Boot环境中，从ApplicationContext获取
     */
    public RestService getRestService() {
        RestService restService = null;
        if (applicationContext != null) {
            try {
                restService = applicationContext.getBean(RestService.class);
            } catch (Exception e) {
                log.warn("Could not find RestService in ApplicationContext", e);
            }
        }
        return restService;
    }

    /**
     * 获取ISymmetricEngine
     * 在Spring Boot环境中，从ApplicationContext获取
     */
    public ISymmetricEngine getEngine() {
        ISymmetricEngine engine = null;
        ServletContext servletContext = getServletContext();
        if (servletContext != null) {
            SymmetricEngineHolder engineHolder = ServletUtils.getSymmetricEngineHolder(servletContext);
            if (engineHolder != null) {
                if (engineHolder.getEngines().size() == 1) {
                    return engineHolder.getEngines().values().iterator().next();
                } else {
                    throw new IllegalStateException("Could not choose a single engine to return.  There are "
                            + engineHolder.getEngines().size() + " engines configured.");
                }
            }
        } else if (applicationContext != null) {
            try {
                engine = applicationContext.getBean(ISymmetricEngine.class);
            } catch (Exception e) {
                log.warn("Could not find ISymmetricEngine in ApplicationContext", e);
            }
        }
        return engine;
    }

    /**
     * 获取指定名称的ISymmetricEngine
     * 在Spring Boot环境中，从ServletContext或ApplicationContext获取
     */
    public ISymmetricEngine getEngine(String name) {
        ISymmetricEngine engine = null;
        ServletContext servletContext = getServletContext();
        if (servletContext != null) {
            SymmetricEngineHolder engineHolder = ServletUtils.getSymmetricEngineHolder(servletContext);
            if (engineHolder != null) {
                return engineHolder.getEngines().get(name);
            }
        }
        return engine;
    }

    /**
     * 等待引擎启动完成
     * 在Spring Boot环境中，通过ServletContext或ApplicationContext获取引擎
     */
    public void waitForEnginesToComeOnline(long maxWaitTimeInMs) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        ServletContext servletContext = getServletContext();
        if (servletContext != null) {
            SymmetricEngineHolder engineHolder = ServletUtils.getSymmetricEngineHolder(servletContext);
            while (engineHolder != null && engineHolder.areEnginesStarting()) {
                AppUtils.sleep(500);
                if ((System.currentTimeMillis() - startTime) > maxWaitTimeInMs) {
                    throw new InterruptedException("Timed out waiting for engines to start");
                }
            }
        }
    }

    /**
     * 实现ServletContextInitializer接口
     * 在Spring Boot应用启动时初始化ServletContext
     */
    @Override
    public void onStartup(ServletContext servletContext) throws javax.servlet.ServletException {
        log.info("Initializing SymmetricDS in Spring Boot environment");
        this.servletContext = servletContext;
        
        // 设置SymmetricDS配置参数
        if (propertiesFile != null) {
            servletContext.setInitParameter(WebConstants.INIT_SINGLE_SERVER_PROPERTIES_FILE, propertiesFile);
            servletContext.setInitParameter(WebConstants.INIT_PARAM_MULTI_SERVER_MODE, Boolean.toString(false));
        } else {
            servletContext.setInitParameter(WebConstants.INIT_PARAM_MULTI_SERVER_MODE, Boolean.toString(true));
        }
        
        // 设置会话Cookie配置
        servletContext.getSessionCookieConfig().setName(cookieName);
        servletContext.getSessionCookieConfig().setHttpOnly(true);
        if (httpsEnabled && !httpEnabled) {
            servletContext.getSessionCookieConfig().setSecure(true);
        }
    }

    /**
     * 实现ApplicationContextAware接口
     * 在Spring Boot应用启动时获取ApplicationContext
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("Setting application context for SymmetricDS");
        this.applicationContext = applicationContext;
    }
}
